/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020-11-5      Ghazigq       first version
 */

#include "board.h"
#include<rtthread.h>
#include<rtdevice.h>

#ifdef RT_USING_SPI

#if defined(BSP_USING_SPI0) || defined(BSP_USING_SPI1) || defined(BSP_USING_SPI2) || defined(BSP_USING_SPI3)

#include "drv_spi.h"
#include <string.h>

//#define DRV_DEBUG
#define LOG_TAG              "drv.spi"
#include <drv_log.h>

enum
{
#ifdef BSP_USING_SPI0
    SPI0_INDEX,
#endif
#ifdef BSP_USING_SPI1
    SPI1_INDEX,
#endif
#ifdef BSP_USING_SPI2
    SPI2_INDEX,
#endif
#ifdef BSP_USING_SPI3
    SPI3_INDEX,
#endif
};

static struct nrf_spi_config spi_config[] =
{
#ifdef BSP_USING_SPI0
    {
        NRFX_SPIM_INSTANCE(0),
        "spi0",
        BSP_SPI0_SCK_PIN,
        BSP_SPI0_MOSI_PIN,
        BSP_SPI0_MISO_PIN,
        RT_NULL,
    },
#endif

#ifdef BSP_USING_SPI1
    {
        NRFX_SPIM_INSTANCE(1),
        "spi1",
        BSP_SPI1_SCK_PIN,
        BSP_SPI1_MOSI_PIN,
        BSP_SPI1_MISO_PIN,
        RT_NULL,
    },
#endif

#ifdef BSP_USING_SPI2
    {
        NRFX_SPIM_INSTANCE(2),
        "spi2",
        BSP_SPI2_SCK_PIN,
        BSP_SPI2_MOSI_PIN,
        BSP_SPI2_MISO_PIN,
        RT_NULL,
    },
#endif

#ifdef BSP_USING_SPI3
    {
        NRFX_SPIM_INSTANCE(3),
        "spi3",
        BSP_SPI3_SCK_PIN,
        BSP_SPI3_MOSI_PIN,
        BSP_SPI3_MISO_PIN,
        RT_NULL,
    },
#endif
};

static uint8_t tx_default_buff[20] = {0xff};
static struct nrf_spi spi_bus_obj[sizeof(spi_config) / sizeof(spi_config[0])] = {0};

static rt_err_t nrf_spi_init(struct nrf_spi *spi_drv, struct rt_spi_configuration *cfg)
{
    RT_ASSERT(spi_drv != RT_NULL);
    RT_ASSERT(cfg != RT_NULL);

    nrfx_spim_config_t config_spim = NRFX_SPIM_DEFAULT_CONFIG;
    config_spim.sck_pin        = spi_drv->config->sck_pin;
    config_spim.mosi_pin       = spi_drv->config->mosi_pin;
    config_spim.miso_pin       = spi_drv->config->miso_pin;
    config_spim.mode           = cfg->mode & (RT_SPI_CPHA | RT_SPI_CPOL);

    if (cfg->mode & RT_SPI_MSB)
        config_spim.bit_order = NRF_SPIM_BIT_ORDER_MSB_FIRST;
    else
        config_spim.bit_order = NRF_SPIM_BIT_ORDER_LSB_FIRST;

    if (cfg->max_hz >= 4000000)
        config_spim.frequency = NRF_SPIM_FREQ_4M;
    else if (cfg->max_hz >= 2000000)
        config_spim.frequency = NRF_SPIM_FREQ_2M;
    else if (cfg->max_hz >= 1000000)
        config_spim.frequency = NRF_SPIM_FREQ_1M;
    else if (cfg->max_hz >= 500000)
        config_spim.frequency = NRF_SPIM_FREQ_500K;
    else if (cfg->max_hz >= 250000)
        config_spim.frequency = NRF_SPIM_FREQ_250K;
    else
        config_spim.frequency = NRF_SPIM_FREQ_125K;

#if defined(SPIM_FREQUENCY_FREQUENCY_M16) || defined(__NRFX_DOXYGEN__)
    if (cfg->max_hz >= 16000000)
        config_spim.frequency      = NRF_SPIM_FREQ_16M;
#endif

#if defined(SPIM_FREQUENCY_FREQUENCY_M32) || defined(__NRFX_DOXYGEN__)
    if (cfg->max_hz >= 32000000)
        config_spim.frequency      = NRF_SPIM_FREQ_32M;
#endif

    if(spi_drv->config->init == 0)
    {
        if(NRFX_SUCCESS != nrfx_spim_init(&spi_drv->config->p_instance, &config_spim, RT_NULL, RT_NULL))
        {
            LOG_E("%s init failed", spi_drv->config->bus_name);
            return -RT_ERROR;
        }
        else
            spi_drv->config->init = 1;

        //    nrf_spim_int_enable(spi_drv->config->p_instance.p_reg, NRF_SPIM_INT_END_MASK);
        //    NRFX_IRQ_PRIORITY_SET(nrfx_get_irq_number(spi_drv->config->p_instance.p_reg), config_spim.irq_priority);
        //    NRFX_IRQ_ENABLE(nrfx_get_irq_number(spi_drv->config->p_instance.p_reg));
    }
    else
    {
        nrf_spim_frequency_set(spi_drv->config->p_instance.p_reg, config_spim.frequency);
        nrf_spim_configure(spi_drv->config->p_instance.p_reg, config_spim.mode, config_spim.bit_order);
    }

    LOG_D("%s init done", spi_drv->config->bus_name);
    return RT_EOK;
}

static rt_uint32_t spixfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    uint32_t result = NRFX_SUCCESS;
    uint8_t *p_rx = RT_NULL;
    uint8_t *p_tx = RT_NULL;

    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(device->bus != RT_NULL);
    RT_ASSERT(device->bus->parent.user_data != RT_NULL);
    RT_ASSERT(message != RT_NULL);

    struct nrf_spi *spi_drv =  rt_container_of(device->bus, struct nrf_spi, spi_bus);
    uint32_t *cs = device->parent.user_data;

    if (message->cs_take)
        nrf_gpio_pin_clear(*cs);

    LOG_D("%s transfer prepare and start", spi_drv->config->bus_name);
    LOG_D("%s sendbuf: %X, recvbuf: %X, length: %d",
          spi_drv->config->bus_name,
          (uint32_t)message->send_buf,
          (uint32_t)message->recv_buf, message->length);

    nrfx_spim_xfer_desc_t spim_xfer_desc =
    {
        .p_tx_buffer = message->send_buf,
        .tx_length   = message->length,
        .p_rx_buffer = message->recv_buf,
        .rx_length   = message->length,
    };

    if(spim_xfer_desc.p_tx_buffer == RT_NULL)
    {
        if(message->length <= sizeof(tx_default_buff)) //避免频繁开辟
            spim_xfer_desc.p_tx_buffer = tx_default_buff;
        else
        {
            p_tx = rt_malloc(message->length);
            if(p_tx == RT_NULL)
                return 0;
            else
            {
                rt_memset(p_tx, 0xff, message->length);
                spim_xfer_desc.p_tx_buffer = p_tx;
            }
        }
    }
    if(spim_xfer_desc.p_rx_buffer == RT_NULL)
    {
        p_rx = rt_malloc(message->length);
        if(p_rx == RT_NULL)
            return 0;
        else
            spim_xfer_desc.p_rx_buffer = p_rx;
    }
    result = nrfx_spim_xfer(&spi_drv->config->p_instance, &spim_xfer_desc, 0);

    if(result != NRFX_SUCCESS)
        message->length = 0;

    if(p_rx != RT_NULL)
        rt_free(p_rx);
    if(p_tx != RT_NULL)
        rt_free(p_tx);
//    rt_sem_take(spi_drv->config->sem, RT_WAITING_FOREVER);
    if (message->cs_release)
        nrf_gpio_pin_set(*cs);
    return message->length;
}

static rt_err_t spi_configure(struct rt_spi_device *device,
                              struct rt_spi_configuration *configuration)
{
    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(configuration != RT_NULL);

    struct nrf_spi *spi_drv =  rt_container_of(device->bus, struct nrf_spi, spi_bus);
    spi_drv->cfg = configuration;

    return nrf_spi_init(spi_drv, configuration);
}

rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, uint32_t cs_gpio_pin)
{
    RT_ASSERT(bus_name != RT_NULL);
    RT_ASSERT(device_name != RT_NULL);

    rt_err_t result;
    struct rt_spi_device *spi_device;
    uint32_t *cs_pin = RT_NULL;

    /* initialize the cs pin && select the slave*/
    nrf_gpio_pin_set(cs_gpio_pin);
    nrf_gpio_cfg_output(cs_gpio_pin);

    /* attach the device to spi bus*/
    spi_device = (struct rt_spi_device *)rt_malloc(sizeof(struct rt_spi_device));
    RT_ASSERT(spi_device != RT_NULL);
    cs_pin = (uint32_t *)rt_malloc(sizeof(uint32_t));
    RT_ASSERT(cs_pin != RT_NULL);
    *cs_pin = cs_gpio_pin;

    result = rt_spi_bus_attach_device(spi_device, device_name, bus_name, (void *)cs_pin);

    if (result != RT_EOK)
    {
        LOG_E("%s attach to %s faild, %d\n", device_name, bus_name, result);
    }

    RT_ASSERT(result == RT_EOK);

    LOG_D("%s attach to %s done", device_name, bus_name);

    return result;
}

static const struct rt_spi_ops nrf_spi_ops =
{
    .configure = spi_configure,
    .xfer = spixfer,
};

static int rt_hw_spi_bus_init(void)
{
    rt_err_t result;

    rt_memset(tx_default_buff, 0xff, sizeof(tx_default_buff));
    for (int i = 0; i < sizeof(spi_config) / sizeof(spi_config[0]); i++)
    {
        spi_bus_obj[i].config = &spi_config[i];
        spi_bus_obj[i].spi_bus.parent.user_data = &spi_config[i];

        spi_bus_obj[i].config->init = 0;

        spi_bus_obj[i].config->sem = rt_sem_create(spi_config[i].bus_name, 0, RT_IPC_FLAG_FIFO);
        RT_ASSERT(spi_bus_obj[i].config->sem != RT_NULL);

        result = rt_spi_bus_register(&spi_bus_obj[i].spi_bus, spi_config[i].bus_name, &nrf_spi_ops);
        RT_ASSERT(result == RT_EOK);

        LOG_D("%s bus init done", spi_config[i].bus_name);
    }

    return result;
}
INIT_BOARD_EXPORT(rt_hw_spi_bus_init);

#ifdef BSP_USING_SPI0
void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    if (nrf_spim_event_check(spi_config[SPI0_INDEX].p_instance.p_reg, NRF_SPIM_EVENT_END))
    {
        nrf_spim_event_clear(spi_config[SPI0_INDEX].p_instance.p_reg, NRF_SPIM_EVENT_END);

        LOG_D("%s transfer completed.", spi_config[SPI0_INDEX].bus_name);

        if(spi_config[SPI0_INDEX].sem)
            rt_sem_release(spi_config[SPI0_INDEX].sem);
    }

    /* leave interrupt */
    rt_interrupt_leave();
}
#endif

#ifdef BSP_USING_SPI1
void SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1_IRQHandler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    if (nrf_spim_event_check(spi_config[SPI1_INDEX].p_instance.p_reg, NRF_SPIM_EVENT_END))
    {
        nrf_spim_event_clear(spi_config[SPI1_INDEX].p_instance.p_reg, NRF_SPIM_EVENT_END);

        LOG_D("%s transfer completed.", spi_config[SPI1_INDEX].bus_name);

        if(spi_config[SPI0_INDEX].sem)
            rt_sem_release(spi_config[SPI0_INDEX].sem);

    }

    /* leave interrupt */
    rt_interrupt_leave();
}
#endif

#ifdef BSP_USING_SPI2
void SPIM2_SPIS2_SPI2_IRQHandler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    if (nrf_spim_event_check(spi_config[SPI2_INDEX].p_instance.p_reg, NRF_SPIM_EVENT_END))
    {
        nrf_spim_event_clear(spi_config[SPI2_INDEX].p_instance.p_reg, NRF_SPIM_EVENT_END);

        LOG_D("%s transfer completed.", spi_config[SPI2_INDEX].bus_name);

        if(spi_config[SPI2_INDEX].sem)
            rt_sem_release(spi_config[SPI2_INDEX].sem);
    }

    /* leave interrupt */
    rt_interrupt_leave();
}
#endif

#ifdef BSP_USING_SPI3
void SPIM3_IRQHandler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    if (nrf_spim_event_check(spi_config[SPI3_INDEX].p_instance.p_reg, NRF_SPIM_EVENT_END))
    {
        nrf_spim_event_clear(spi_config[SPI3_INDEX].p_instance.p_reg, NRF_SPIM_EVENT_END);

        LOG_D("%s transfer completed.", spi_config[SPI3_INDEX].bus_name);

        if(spi_config[SPI3_INDEX].sem)
            rt_sem_release(spi_config[SPI3_INDEX].sem);
    }

    /* leave interrupt */
    rt_interrupt_leave();
}
#endif

#endif /* BSP_USING_SPI1 || BSP_USING_SPI2 || BSP_USING_SPI3 */
#endif /* RT_USING_SPI */
